/**
 * 对 uni 的网络请求再次进行包装，使其更容易使用
 * 自动化处理 token ，自动维持登录状态，自动跳转登录
 * 自动化处理加解密（暂未实现）
 */
import Api from "@/utils/Api";

//许的版本
const CODE_SUCCESS = 200; //请求成功code
const CODE_ERROR = 500; //请求失败code
const CODE_NOT_LOGIN = 403; //未登录code
const CODE_DISABLE = 404; //账号被禁用code
const RESULT_NAME_CODE = "code"; //返回数据中 code 的名称
const RESULT_NAME_RESULT = "data"; //返回数据中 result 的名称
const RESULT_NAME_MESSAGE = "msg"; //返回数据中 message 的名称
const RESULT_NAME_TOKEN = "token"; //返回数据中 token 的名称
export const baseUrl = "https://my.soft.haoyangsoft.com";

let serverPath = baseUrl;
// #ifdef H5
serverPath = "/h5-api";
// #endif

var enableCrypto = false; //是否加密
var commonConfig = {}; //从服务器获取的客户端配置
var fileServer = ''; //文件服务器地址
var notBackRoute = '' //登录弹窗点击取消按钮不需要返回上一页的页面 route
var token = uni.getStorageSync("token");
// console.log({token})
// if (typeof token !== 'string' || token.split(".").length !== 3) { //校验 token 完整性
//   uni.setStorageSync("token", "");
//   token = "";
// }


/**
 * 将一段 url 进行处理，智能的根据需要加上上下文路径（当前项目名）
 * @param {String} url 需要处理的地址
 */
function ctx(url) {
  //判断上下文路径是否存在、url是否为string、是否为已经加上项目名、是否为绝对路径
  // if (!serverPath || typeof url !== "string" || url === serverPath ||
  //     url.startsWith(serverPath + "/") || !url.startsWith("/")) {
  //   return url;
  // }
  if (url.startsWith("http") || url.startsWith('/map-api')) return url;
  return serverPath + url;
}

/**
 * 请求
 * @param config {FetchConfig}
 * @return {Promise<*>}
 */
export function fetch(config) {
  let {url, data, success, common, error, method, contentType, notice, params, loading, allowSuccess} = config

  // params.openid = uni.getStorageSync("openid")

  if (params) url = uni.core.addQueryString(url, params)
  if (!contentType) {
    contentType = "application/x-www-form-urlencoded";
  }
  if (data) {
    for (let key in data) {
      if (data.hasOwnProperty(key)) {
        let value = data[key];
        // if (Array.isArray(value))
        //   data[key] = value.join(",")

        if (value === undefined || value === null)
          delete data[key];

      }
    }
  }


  return new Promise((resolve, reject) => {

    success = (res, data) => {

      let returnValue = allowSuccess ? data : res;
      resolve(returnValue);
    }
    error = (code, message, errorFunc) => {
      errorFunc(code, message);
      reject();
    }

    ajax(url, data, success, common, error, method, contentType, notice, loading, allowSuccess)

  })
}

export function ajax(url, data, success, common, error, method, contentType, notice, loading, allowSuccess) {
  url = ctx(url);

  let header = {}
  if (contentType !== undefined && contentType !== null) {
    header["Content-Type"] = contentType;
  }

  // if (token) {
  header["Authorization"] = token;
  // }

  if (loading) uni.showLoading({title: "加载中..."})
  let complete = getComplete(success, common, error, notice, loading, allowSuccess);
  uni.request({url, method, header, data, complete: complete.complete});

  return complete.promise;
}

/**
 * 获取请求结果统一处理函数
 */
function getComplete(success, common, error, notice, loading, allowSuccess) {
  let promise = null;
  let resolve = null;
  let reject = null;
  loading = !!loading;
  if (!success && !common && !error) {
    promise = new Promise((_resolve, _reject) => {
      resolve = _resolve;
      reject = _reject;
    })
  }

  function complete(res) {
    uni.stopPullDownRefresh()
    if (loading) uni.hideLoading();

    let data = res.data;

    if (!data) { //兼容处理 data 不存在的情况
      console.log(res)
      data = {};
      data[RESULT_NAME_CODE] = CODE_ERROR;
      data[RESULT_NAME_MESSAGE] = "请求失败！";
    }

    //上传文件时返回值不会直接转对象，这里兼容处理一下
    if (typeof data === "string" && data.trim().startsWith("{")) {
      data = JSON.parse(data)
    }

    if (data[RESULT_NAME_TOKEN]) { //返回值中包含了token
      token = data[RESULT_NAME_TOKEN];
      uni.setStorageSync("token", data[RESULT_NAME_TOKEN])
    }

    let code = data[RESULT_NAME_CODE];
    let result = data[RESULT_NAME_RESULT];
    let message = data[RESULT_NAME_MESSAGE];

    let noticeData = {code, result, message, data, token}

    function callNotice(method) {
      if (typeof notice === "function") {
        try {
          noticeData.method = method;
          notice(noticeData);
        } catch (e) {
          console.error(e);
        }
      }
    }

    if (code == CODE_SUCCESS || allowSuccess) { //成功处理请求
      callNotice("success");
      if (resolve) return resolve(result);
      if (typeof success === "function") {
        try {
          success(result, data);
        } catch (e) {
          console.error(e);
        }
      }
      callNotice("common");
      if (typeof common === "function") {
        common(result, data);
      }
    } else {
      if (reject) return reject({code, message, result, data, errorFunc});

      callNotice("common");
      if (typeof common === "function") {
        try {
          common(result, data, message, errorFunc);
        } catch (e) {
          console.error(e);
        }
      }

      callNotice("error");
      if (typeof error === "function") {
        error(code, message, errorFunc)
      } else {
        errorFunc(code, message)
      }
    }

  }

  return {
    complete,
    promise
  }
}


function errorFunc(code, message) {

  // uni.showModal({
  //     title: "失败",
  //     content: message
  // })
  uni.core.showToast(message);

  if (code === CODE_DISABLE) { //账号已被禁用
    uni.core.loginOut();
  } else if (code === CODE_NOT_LOGIN) { //未登录
    if (uni.globalData.requestForceLogin) { //强制登录，直接跳到登录页
      uni.core.toLogin();
    } else { //弹出提示框，点击确定去登录
      console.log("请求拦截登录")
      uni.showModal({
        title: "立即登录",
        content: "该功能需要登录！",
        success: e => {
          if (e.confirm) {
            uni.core.toLogin();
          } else { //点击取消按钮，如果有上一页则返回上一页
            navigateBack();
          }
        }
      })
    }

  }


}

export function fetchFile(config) {

  let {url, name, filePath, file, formData, success, common, error, notice, loading, allowSuccess} = config

  return new Promise((resolve, reject) => {

    success = (res, data) => {

      let returnValue = allowSuccess ? data : res;
      resolve(returnValue);
    }

    error = (code, message, errorFunc) => {
      errorFunc(code, message);
      reject();
    }

    uploadFile(url, name, filePath, file, formData, success, common, error, notice, loading, allowSuccess)

  })
}

function uploadFile(url, name, filePath, file, formData, success, common, error, notice, loading, allowSuccess) {
  if (loading) uni.showLoading({title: "上传中..."})
  let complete = getComplete(success, common, error, notice, loading, allowSuccess);
  let options = {
    url: ctx(url),
    name: name || 'file',
    formData,
    header: {
      "Authorization": token
    },
    complete: complete.complete
  }
  filePath && (options.filePath = filePath);
  file && (options.file = filePath);

  uni.uploadFile(options)
  return complete.promise;
}

/**
 * 模拟 ajax 成功回调，不传回调函数会返回 Promise 对象，支持同步调用
 */
function imitateAjax(result, success, common, error) {
  var data = {};
  data[RESULT_NAME_CODE] = CODE_SUCCESS;
  data[RESULT_NAME_RESULT] = result;
  data[RESULT_NAME_MESSAGE] = "请求成功";
  var complete = getComplete(success, common, error)
  setTimeout(_ => complete.complete({
    data
  }));
  return complete.promise;
}

/**
 * 检测并判断是否要返回上一页（当用户取消登录的时候）
 */
function navigateBack() {
  const pages = getCurrentPages();
  if (pages < 2) return;
  if (pages[pages.length - 1].route === notBackRoute) return;

  uni.navigateBack();
}


const req = {
  ctx,
  /** 以表单形式发送 GET 请求，不传回调函数会返回 Promise 对象，支持同步调用
   * @param {String} url 请求地址
   * @param {Object} data 参数
   * @param {Function} success  成功回调函数，第一个参数为精简后的返回值，第二个参数为全部的返回值
   * @param {Function} common 通用处理函数，主要用来清除 loading 状态之类的，调用时机在 success 之后 error 之前，第一、二个参数与 success 保持一致，剩下的参数与 error 保持一致
   * @param {Function} error 失败回调函数,第一个参数是错误码，第二个参数为错误消息，第三个参数是默认的错误处理函数，不传使用默认的，会出现一个弹窗提示错误
   * @param {Funciton} notice 调用 success, common, error 事件的前置通知的回调函数，主要用来记录缓存
   */
  get(url, data, success, common, error, notice) {
    return ajax(url, data, success, common, error, "GET", "application/x-www-form-urlencoded;charset=UTF-8", notice)
  },
  /** 以JSON发送 POST 请求，不传回调函数会返回 Promise 对象，支持同步调用
   * @param {String} url 请求地址
   * @param {Object} data 参数
   * @param {Function} success  成功回调函数，第一个参数为精简后的返回值，第二个参数为全部的返回值
   * @param {Function} common 通用处理函数，主要用来清除 loading 状态之类的，调用时机在 success 之后 error 之前，第一、二个参数与 success 保持一致，剩下的参数与 error 保持一致
   * @param {Function} error 失败回调函数,第一个参数是错误码，第二个参数为错误消息，第三个参数是默认的错误处理函数，不传使用默认的，会出现一个弹窗提示错误
   * @param {Funciton} notice 调用 success, common, error 事件的前置通知的回调函数，主要用来记录缓存
   */
  postJ(url, data, success, common, error, notice) {
    return ajax(url, data, success, common, error, "POST", "application/json;charset=UTF-8", notice)
  },
  /** 以表单形式发送 POST 请求，不传回调函数会返回 Promise 对象，支持同步调用
   * @param {String} url 请求地址
   * @param {Object} data 参数
   * @param {Function} success  成功回调函数，第一个参数为精简后的返回值，第二个参数为全部的返回值
   * @param {Function} common 通用处理函数，主要用来清除 loading 状态之类的，调用时机在 success 之后 error 之前，第一、二个参数与 success 保持一致，剩下的参数与 error 保持一致
   * @param {Function} error 失败回调函数,第一个参数是错误码，第二个参数为错误消息，第三个参数是默认的错误处理函数，不传使用默认的，会出现一个弹窗提示错误
   * @param {Funciton} notice 调用 success, common, error 事件的前置通知的回调函数，主要用来记录缓存
   */
  post(url, data, success, common, error, notice) {
    return ajax(url, data, success, common, error, "POST", "application/x-www-form-urlencoded;charset=UTF-8", notice)
  },
  /**
   * 上传文件，不传回调函数会返回 Promise 对象，支持同步调用
   * @param {Object} filePath 文件路径
   * @param {Object} success 成功回调函数，第一个参数为精简后的返回值，第二个参数为全部的返回值
   * @param {Object} common 通用处理函数，主要用来清除 loading 状态之类的，调用时机在 success 之后 error 之前，第一、二个参数与 success 保持一致，剩下的参数与 error 保持一致
   * @param {Object} error 失败回调函数,第一个参数是错误码，第二个参数为错误消息，第三个参数是默认的错误处理函数，不传使用默认的，会出现一个弹窗提示错误
   */
  uploadFile(filePath, success, common, error) {
    return uploadFile("/client/im/common/upload-file", "files", filePath, {}, success, common, error)
  },
  /**
   * 上传用户头像，不传回调函数会返回 Promise 对象，支持同步调用
   * @param {Object} filePath 文件路径
   */
  uploadUserHead(filePath, success, common, error) {
    return uploadFile("/client/user/upload-head", "file", filePath, {}, success, common, error)
  },
  /**
   * 获取 token
   */
  getToken() {
    return token;
  },
  /**
   * 设置 token，如果设置为空字符串等同于退出登录。
   */
  setToken(_token) {
    uni.setStorageSync("token", _token);
    return token = _token;
  },
  /**
   * 设置服务器路径，例如 http://127.0.0.1:8080/jeecg-boot
   * @param {String} path 服务器路径，例如 http://127.0.0.1:8080/jeecg-boot
   */
  setServerPath(path) {
    serverPath = path;
  },
  /** 开关加密功能
   * @param {Object} enable 是否启用加密
   */
  switchCrypto(enable) {
    enableCrypto = enable;
  },

  /**
   * 从服务器获取并用户信息，如果未登录则会触发登录
   * 不传回调函数会返回 Promise 对象，支持同步调用
   */
  getUserInfo(success, common, error) {
    return Api.getUserInfo().then(res => {
      if (res) {
        uni.setStorageSync("userInfo", res);
        success(res)
      }
    })
  },
  /**
   * 从缓存获取用户信息，如果不存在则从服务器获取并用户信息，如果未登录则会触发登录
   * 不传回调函数会返回 Promise 对象，支持同步调用
   */
  getUserInfoCache(success, common, error) {
    var info = uni.getStorageSync("userInfo");
    if (info) {
      return imitateAjax(info, success, common, error);
    } else {
      return req.getUserInfo(success, common, error);
    }
  },
  /**
   * 直接从缓存获取用户信息并返回，不存在则返回空字符串
   */
  getUserInfoForCache() {
    return uni.getStorageSync("userInfo");
  },
  /**
   * 保存用户信息缓存
   */
  setUserInfoCache(userInfo) {
    uni.setStorageSync("userInfo", userInfo);
  },
  getUserInfoCacheCheckMobile() {
    return new Promise((resolve, reject) => {
      this.getUserInfoCache(res => {
        // console.log("获取用户信息", res)
        let {mobile} = res;
        if (!mobile) {
          // uni.showToast({title: '请授权获取手机号'});
          uni.navigateTo({url: `/pages/login/index2`})
          reject();
        } else {
          resolve(res);
        }
      })
    })
  },
  getUserInfoCheckMobile() {
    return new Promise((resolve, reject) => {
      this.getUserInfo(res => {
        // console.log("获取用户信息", res)
        let {mobile} = res;
        if (!mobile) {
          // uni.showToast({title: '请授权获取手机号'});
          uni.navigateTo({url: `/pages/login/index2`})
          reject();
        } else {
          resolve(res);
        }
      })
    })
  },

  /**
   * 获取一个文件的访问地址，自动检测并拼接上文件服务器地址，支持任意数量参数，按照先后顺序返回第一个有效的url
   */
  getFileUrl() {

    function get(url) {
      if (url) {
        if (url.startsWith('http://') || url.startsWith('https://')) return url;
        return fileServer + url;
      }
    }

    for (var i in arguments) { //遍历传入的所有参数，找到第一个有效的参数并返回
      let url = get(arguments[i]);
      if (url) return url;
    }
    return '';
  },

  /**
   * 获取一组轮播图
   * 不传回调函数会返回 Promise 对象，支持同步调用
   * @param {String} series 组名
   */
  getBannerSeries(series, success, common, error) {
    return this.get("/client/cms/banner/series", {
      series
    }, success, common, error)
  },

  /**
   * 发送验证码
   * 不传回调函数会返回 Promise 对象，支持同步调用
   * @param {String} phone 手机号
   * @param {String} type 验证码类型，login登录，password修改密码
   */
  sendValidationCode(phone, type, success, common, error) {
    return this.postJ("/client/im/common/send-validation-code", {
      phone,
      type
    }, success, common, error)
  },


  /**
   * 设置当前页点击去登录弹窗取消按钮不返回上一页
   */
  cancelToLoginNotNavigateBack() {
    const pages = getCurrentPages();
    notBackRoute = pages[pages.length - 1].route;
    console.log("cancelToLoginNotNavigateBack", notBackRoute);
  },
  /**
   * 从服务器加载配置到本地进行缓存，然后进行初始化操作
   * 不传回调函数会返回 Promise 对象，支持同步调用
   */
  getConfig(success, common, error) {
    return req.get("/client/im/common/config", {}, success, common, error, data => {
      if (data.method !== "success") return;
      commonConfig = data.result;
      uni.globalData.config = data.result; //挂载到全局数据上
      fileServer = uni.core.getRandomItem(commonConfig.fileServer) || '';
    })
  },
  /**
   * 先从缓存获取配置，不存在再服务器加载配置到本地进行缓存，然后进行初始化操作
   * 不传回调函数会返回 Promise 对象，支持同步调用
   */
  getConfigCache(success, common, error) {
    if (commonConfig) {
      return imitateAjax(commonConfig, success, common, error);
    } else {
      return req.getConfig(success, common, error);
    }
  },
  /**
   * 直接获取配置数据，也可以直接通过 uni.globalData.config 使用
   */
  getConfigForCache() {
    return commonConfig;
  }
}

export default req;
